﻿#include "FITKGraphObjectWire.h"

// VTK
#include <vtkUnstructuredGrid.h>
#include <vtkPoints.h>
#include <vtkIntArray.h>
#include <vtkCellData.h>
#include <vtkSmartPointer.h>
#include <vtkCellType.h>
#include <vtkProperty.h>
#include <vtkAppendFilter.h>

// Graph
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor.h"
#include "FITKGraphCommons.h"

// Data
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaWire.h"

namespace Exchange
{
    FITKGraphObjectWire::FITKGraphObjectWire(AbaqusData::FITKDataCase* caseData, Interface::FITKAbaWire* wire)
        : FITKGraphObjectLineBase(caseData, wire)
    {
        if (!wire)
        {
            return;
        }

        // Initialize.
        init();

        // Generate the line poly.
        bool flag = generatePolyData(wire);
        if (!flag)
        {
            return;
        }

        // Generate the wire actor.
        generateGraph();

        // Update the visibility.
        updateVisibility();

        // Set the layer need to be rendered.
        setRenderLayer(0, 1);

        // Save the graph information.
        m_graphInfo.Type = WireGraph;
        m_graphInfo.WireId = wire->getDataObjectID();

        // Save if the bounds are fixed or dynamic.
        m_hasFixedBounds = true;
    }

    FITKGraphObjectWire::~FITKGraphObjectWire()
    {
        // Delete the lines data.
        clearData();

        // Delete the append filter.
        deleteVtkObj(m_appendFilter);

        // Delete the grid data for highlighting.
        deleteVtkObj(m_pointsHighlight);
        deleteVtkObj(m_ugridHighlight);
    }

    void FITKGraphObjectWire::init()
    {
        // Create the append filer.
        m_appendFilter = vtkAppendFilter::New();

        // Create the grid data for highlighting.
        m_ugridHighlight = vtkUnstructuredGrid::New();
        m_pointsHighlight = vtkPoints::New();
        m_ugridHighlight->SetPoints(m_pointsHighlight);

        // Create the grid data for advanced-highlighting.
        m_ugridAdv = vtkUnstructuredGrid::New();
        m_pointsAdv = vtkPoints::New();
        m_ugridAdv->SetPoints(m_pointsAdv);

        // Initialize the advance data.
        m_pointsAdv->InsertNextPoint(0., 0., 0.);
        m_pointsAdv->InsertNextPoint(0., 0., 0.);

        vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
        cell->InsertNextId(0);
        cell->InsertNextId(1);
        m_ugridAdv->InsertNextCell(VTKCellType::VTK_LINE, cell);
    }

    void FITKGraphObjectWire::clearAdvanceData()
    {
        // Reset the grid data.
        //@{
        if (m_pointsAdv)
        {
            m_pointsAdv->SetPoint(0, 0., 0., 0.);
            m_pointsAdv->SetPoint(1, 0., 0., 0.);
        }

        resetVtkObj(m_ugridAdv);
        //@}
    }

    void FITKGraphObjectWire::update(bool forceUpdate)
    {
        Interface::FITKAbaWire* wire = dynamic_cast<Interface::FITKAbaWire*>(_dataObj);
        if (!wire)
        {
            return;
        }

        // Check if the lines changed.
        //@{
        bool hasChanged = false;

        // Get the wire's line count.
        int nLine = wire->getDataCount();

        // Check if the lines count changed.
        if (nLine == m_lastLinesInfo.count())
        {
            for (int i = 0; i < nLine; i++)
            {
                // Get the line.
                Interface::FITKAbaAssEle3D2* line = wire->getDataByIndex(i);
                if (!line)
                {
                    continue;
                }

                // Get the ids of the model and node.
                _LineInfo info;
                line->getNode1(info._ModelId_1, info._NodeId_1);
                line->getNode2(info._ModelId_2, info._NodeId_2);

                _LineInfo & savedInfo = m_lastLinesInfo[i];
                if (savedInfo._ModelId_1 != info._ModelId_1 || savedInfo._ModelId_2 != info._ModelId_2 ||
                    savedInfo._NodeId_1 != info._NodeId_1 || savedInfo._NodeId_2 != info._NodeId_2)
                {
                    hasChanged = true;
                    break;
                }
            }
        }
        else
        {
            hasChanged = true;
        }

        if (!hasChanged && !forceUpdate)
        {
            // Update the full visibility first.
            updateVisibility();

            return;
        }
        //@}

        // Clear data first.
        clearData();

        // Generate the wire poly data.
        generatePolyData(wire);
    }

    void FITKGraphObjectWire::highlight(QColor color)
    {
        // If the object is invisible, then return.
        if (!getDataVisibility())
        {
            return;
        }

        // Show the actor for highlighting.
        if (!m_fActorHighlight)
        {
            return;
        }

        // Get the wire highlight color.
        double color3[3]{ 0., 0., 0. };
        if (color.isValid())
        {
            FITKGraphCommons::QColorToDouble3(color, color3);
        }
        else
        {
            FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_wireHighlightColor, color3);
        }

        m_fActorHighlight->GetProperty()->SetColor(color3);
        m_fActorHighlight->SetVisibility(true);

        FITKGraphObject3D::highlight();
    }

    void FITKGraphObjectWire::disHighlight()
    {
        // Hide the actor for highlighting.
        if (m_fActorHighlight)
        {
            m_fActorHighlight->SetVisibility(false);
        }

        FITKGraphObject3D::disHighlight();
    }

    void FITKGraphObjectWire::advanceHighlight(QVariant details, QColor color)
    {
        // Reset data first.
        clearAdvanceData();

        Interface::FITKAbaWire* wireData = dynamic_cast<Interface::FITKAbaWire*>(_dataObj);
        if (!wireData)
        {
            return;
        }

        // Check the given indice of spring pair.
        if (!details.isValid())
        {
            return;
        }

        QVariantList vList = details.toList();
        for (const QVariant & var : vList)
        {
            // Get the line by the given data id.
            int lineId = var.toInt();
            Interface::FITKAbaAssEle3D2* line = wireData->getDataByID(lineId);
            if (!line)
            {
                return;
            }

            // Get the positions.
            double coord_1[3]{ 0., 0., 0. };
            double coord_2[3]{ 0., 0., 0. };
            line->getCoor1(coord_1);
            line->getCoor2(coord_2);

            // Get the ids of the model and node.
            _LineInfo info;
            line->getNode1(info._ModelId_1, info._NodeId_1);
            line->getNode2(info._ModelId_2, info._NodeId_2);

            // Check if node 1 is valid.
            bool node1Valid = false;
            if (info._ModelId_1 > 0 && info._NodeId_1 > 0)
            {
                node1Valid = true;
            }

            // Check if node 2 is valid.
            bool node2Valid = false;
            if (info._ModelId_2 > 0 && info._NodeId_2 > 0)
            {
                node2Valid = true;
            }

            if (node1Valid && node2Valid)
            {
                // Add a line cell to highlight grid data.
                int ptId_1 = m_pointsAdv->InsertNextPoint(coord_1);
                int ptId_2 = m_pointsAdv->InsertNextPoint(coord_2);

                vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
                cell->InsertNextId(ptId_1);
                cell->InsertNextId(ptId_2);
                m_ugridAdv->InsertNextCell(VTKCellType::VTK_LINE, cell);
            }
            else
            {
                int ptId = -1;
                if (node1Valid)
                {
                    ptId = m_pointsAdv->InsertNextPoint(coord_1);
                }

                if (node2Valid)
                {
                    ptId = m_pointsAdv->InsertNextPoint(coord_2);
                }

                vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
                cell->InsertNextId(ptId);
                m_ugridAdv->InsertNextCell(VTKCellType::VTK_VERTEX, cell);
            }
        }

        // Update the grid data.
        m_ugridAdv->Modified();

        // Get the wire highlight color.
        double color3[3]{ 0., 0., 0. };
        if (color.isValid())
        {
            FITKGraphCommons::QColorToDouble3(color, color3);
        }
        else
        {
            FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_wireHighlightColor, color3);
        }

        // Show the actor and set the color.
        if (m_fActorAdvHighlight)
        {
            m_fActorAdvHighlight->SetVisibility(true);
            m_fActorAdvHighlight->GetProperty()->SetColor(color3);
        }

        FITKGraphObject3D::advanceHighlight();
    }

    void FITKGraphObjectWire::disAdvanceHighlight()
    {
        // Hide the actor only.
        if (m_fActorAdvHighlight)
        {
            m_fActorAdvHighlight->SetVisibility(false);
        }

        // Reset the advance data.( Not necesseray. )
        clearAdvanceData();

        FITKGraphObject3D::disAdvanceHighlight();
    }

    void FITKGraphObjectWire::setVisible(bool visibility)
    {
        // Set the visibility for line actor.
        if (m_fActor)
        {
            m_fActor->SetVisibility(visibility);
        }

        // Show or hide the highlight actor.
        if (m_fActorHighlight)
        {
            m_fActorHighlight->SetVisibility(visibility && m_isHighlighting);
        }
    }

    void FITKGraphObjectWire::setPickable(bool isPickable, int dim)
    {
        Q_UNUSED(dim);

        if (m_fActor)
        {
            m_fActor->SetPickable(isPickable);
        }
    }

    bool FITKGraphObjectWire::generatePolyData(Interface::FITKAbaWire* wire)
    {
        if (!m_pointsHighlight || !m_ugridHighlight)
        {
            return false;
        }

        // Get the wire's line count.
        int nLine = wire->getDataCount();

        for (int i = 0; i < nLine; i++)
        {
            // Get the line.
            Interface::FITKAbaAssEle3D2* line = wire->getDataByIndex(i);
            if (!line)
            {
                continue;
            }

            // Get the line object id.
            int lineId = line->getDataObjectID();

            // Generate line grid and extractor.
            vtkUnstructuredGrid* ugrid = vtkUnstructuredGrid::New();
            vtkPoints* points = vtkPoints::New();
            vtkIntArray* idArray = vtkIntArray::New();
            idArray->SetName(ID_ARRAY);
            ugrid->SetPoints(points);
            ugrid->GetCellData()->AddArray(idArray);

            // Add to the data list.
            m_pointsList.push_back(points);
            m_ugridList.push_back(ugrid);
            m_idArrayList.push_back(idArray);

            // Add to the append filter.
            m_appendFilter->AddInputData(ugrid);

            // Get the ids of the model and node.
            _LineInfo info;
            line->getNode1(info._ModelId_1, info._NodeId_1);
            line->getNode2(info._ModelId_2, info._NodeId_2);

            // Store the ids.
            m_lastLinesInfo.push_back(info);

            // Get the coordinate.
            double coord_1[3]{ 0., 0., 0. };
            double coord_2[3]{ 0., 0., 0. };
            line->getCoor1(coord_1);
            line->getCoor2(coord_2);

            // Check if node 1 is valid.
            bool node1Valid = false;
            if (info._ModelId_1 > 0 && info._NodeId_1 > 0)
            {
                node1Valid = true;
            }

            // Check if node 2 is valid.
            bool node2Valid = false;
            if (info._ModelId_2 > 0 && info._NodeId_2 > 0)
            {
                node2Valid = true;
            }

            if (node1Valid && node2Valid)
            {
                // Add a line cell to highlight grid data.
                int ptId_1 = m_pointsHighlight->InsertNextPoint(coord_1);
                int ptId_2 = m_pointsHighlight->InsertNextPoint(coord_2);

                vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
                cell->InsertNextId(ptId_1);
                cell->InsertNextId(ptId_2);
                m_ugridHighlight->InsertNextCell(VTKCellType::VTK_LINE, cell);

                // Check if the two points are the same.
                if (qFuzzyCompare(coord_1[0], coord_2[0]) &&
                    qFuzzyCompare(coord_1[1], coord_2[1]) &&
                    qFuzzyCompare(coord_1[2], coord_2[2]))
                {
                    // Add a vertex cell.
                    int ptId = points->InsertNextPoint(coord_1);

                    vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
                    cell->InsertNextId(ptId);
                    ugrid->InsertNextCell(VTKCellType::VTK_VERTEX, cell);
                }
                else
                {
                    // Generate stipple line.
                    FITKGraphCommons::AddStippleLine(ugrid, coord_1, coord_2);
                }
            }
            else
            {
                int ptId = -1;
                int ptIdHl = -1;

                if (node1Valid)
                {
                    ptId = points->InsertNextPoint(coord_1);
                    ptIdHl = m_pointsHighlight->InsertNextPoint(coord_1);
                }

                if (node2Valid)
                {
                    ptId = points->InsertNextPoint(coord_2);
                    ptIdHl = m_pointsHighlight->InsertNextPoint(coord_2);
                }

                vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
                cell->InsertNextId(ptId);
                ugrid->InsertNextCell(VTKCellType::VTK_VERTEX, cell);

                vtkSmartPointer<vtkIdList> cellForHl = vtkSmartPointer<vtkIdList>::New();
                cellForHl->InsertNextId(ptIdHl);
                m_ugridHighlight->InsertNextCell(VTKCellType::VTK_VERTEX, cellForHl);
            }

            // Fill id array.
            int nCell = ugrid->GetNumberOfCells();
            for (int j = 0; j < nCell; j++)
            {
                idArray->InsertNextValue(lineId);
            }
        }

        return true;
    }

    void FITKGraphObjectWire::generateGraph()
    {
        if (!m_appendFilter || !m_ugridHighlight)
        {
            return;
        }

        // Get the wire color.
        double color3[3]{ 0., 0., 0. };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_wireColor, color3);

        // Get the wire vertex color.
        double colorVert3[3]{ 0., 0., 0. };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_wireVertexColor, colorVert3);

        // Generate the actor.
        m_fActor = FITKGraphActor::New();
        m_fActor->setGraphObject(this);
        m_fActor->SetPickable(false);
        m_fActor->SetVisibility(true);
        m_fActor->setInputConnection(m_appendFilter->GetOutputPort());
        m_fActor->GetProperty()->SetColor(color3);
        m_fActor->GetProperty()->SetLineWidth(FITKGraphCommons::s_highlightLineWidth);
        m_fActor->GetProperty()->SetPointSize(FITKGraphCommons::s_highlightPointSize);
        m_fActor->GetProperty()->SetRepresentation(2);
        m_fActor->GetProperty()->SetVertexVisibility(true);
        m_fActor->GetProperty()->SetVertexColor(colorVert3);
        addActor(m_fActor);

        // Get the wire highlight color.
        double colorHl3[3]{ 0., 0., 0. };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_wireHighlightColor, colorHl3);

        // Generate the actor for highlighting.
        m_fActorHighlight = FITKGraphActor::New();
        m_fActorHighlight->setGraphObject(this);
        m_fActorHighlight->SetPickable(false);
        m_fActorHighlight->SetVisibility(false);
        m_fActorHighlight->setInputDataObject(m_ugridHighlight);
        m_fActorHighlight->GetProperty()->SetLineWidth(FITKGraphCommons::s_highlightLineWidth - 1);
        m_fActorHighlight->GetProperty()->SetPointSize(FITKGraphCommons::s_highlightPointSize - 1);
        m_fActorHighlight->GetProperty()->SetRepresentation(1);
        m_fActorHighlight->GetProperty()->SetColor(colorHl3);
        addActor(m_fActorHighlight);

        // Generate the actor for highlighting.
        m_fActorAdvHighlight = FITKGraphActor::New();
        m_fActorAdvHighlight->setGraphObject(this);
        m_fActorAdvHighlight->SetPickable(false);
        m_fActorAdvHighlight->SetVisibility(false);
        m_fActorAdvHighlight->setInputDataObject(m_ugridAdv);
        m_fActorAdvHighlight->GetProperty()->SetLineWidth(FITKGraphCommons::s_highlightLineWidth + 2);
        m_fActorAdvHighlight->GetProperty()->SetPointSize(FITKGraphCommons::s_highlightPointSize - 1);
        m_fActorAdvHighlight->GetProperty()->SetRepresentation(1);
        m_fActorAdvHighlight->GetProperty()->SetColor(colorHl3);
        addActor(m_fActorAdvHighlight);
    }

    void FITKGraphObjectWire::clearData()
    {
        // Clear append filter.
        if (m_appendFilter)
        {
            m_appendFilter->RemoveAllInputConnections(0);
            m_appendFilter->RemoveAllInputs();
        }

        // Delete the line points list.
        deleteVtkObjs(m_pointsList);

        // Delete the line grid list.
        deleteVtkObjs(m_ugridList);

        // Delete the grid id array list.
        deleteVtkObjs(m_idArrayList); 

        // Clear the model info.
        m_lastLinesInfo.clear();

        // Clear the highlight grid data.
        resetVtkObj(m_pointsHighlight);
        resetVtkObj(m_ugridHighlight);

        // Reset the advance data.( Not necesseray. )
        clearAdvanceData();
    }
}   // namespace Exchange